# 从当前模块导入蓝图对象,
from flask import request,current_app,abort,make_response,jsonify,session
from info.models import User
from info import redis_store,db
from info.utils import constants
from info.utils.response_code import RET
from info.utils.captcha.captcha import captcha
from . import passport_bp
import json
import re
import random
from info.utils.yuntongxun.sms import CCP
from datetime import datetime


@passport_bp.route('/sms_code',methods=["POST"])
def send_sms():
    """点击发送短信验证码接口"""
    """
    1.获取参数
        1.1 手机号码，用户填写的图片验证码真实值，编号
    2.校验参数
        2.1 判断 手机号码，用户填写的图片验证码真实值，编号是否为空
        2.2 手机号码格式校验
    3.逻辑处理
        3.1 根据编号去获取redis中存储的图片验证码真实值
            3.1.1 image_code_id有值，删除
            3.1.2 image_code_id没有值，表示编号过期
        3.2 对比用户填写的真实值和后端获取的验证码真实值是否一致
        一致：发送短信验证码
        不一致：验证码填写错误
        3.3 保存短信验证码到redis
    4.返回值处理
    """

    # 1.获取参数（json类型参数） ,前后端交互,json数据类型是最好的类型.(接口设计文档也是如此设计)
    # 1.1 手机号码，用户填写的图片验证码真实值，编号
    # request.json 获取到数据会自动转换成python对象（dict or list）(flask源码封装的一个方法)
    # param_dict = json.load(request.data) #,之前用这个方法,
    param_dict = request.json
    mobile = param_dict.get("mobile")
    image_code = param_dict.get("image_code")
    image_code_id = param_dict.get("image_code_id")

    # 2.校验参数
    # 2.1 判断 手机号码，用户填写的图片验证码真实值，编号是否为空
    # all方法,后面跟一个列表,这里用了取反,只要有一个值为空,就返回false
    if not all([mobile,image_code,image_code_id]):
        # 返回错误给前端展示
        # 这里返回json数据的格式用的是一种前后端都遵守的牛逼套路,如下返回的格式遵循了response_code.py内部定义的格式,这个文件是新考进去的,解释公司也有自己相应的格式
        # 参数名字按接口设计文档写的,
        return jsonify(errno=RET.PARAMERR, errmsg="提交参数不足")
    # 2.2 手机号码格式校验
    if not re.match('1[3578][0-9]{9}',mobile):
        return jsonify(errno=RET.PARAMERR,errmsg="手机号码格式错误")



    # 3.逻辑处理
    # 要取值了,要操作数据库了,以后接口内,凡是读数据库,都用抛出异常的格式,将异常记录日志
    try:
        # 3.1 根据编号去获取redis中存储的图片验证码真实值
        real_image_code = redis_store.get("imagecode_%s" %image_code_id)
        # 3.1.1 image_code_id有值，删除 防止下次多次使用同一个real_image_code来访问
        if real_image_code:
            redis_store.delete("imagecode_%s" %image_code_id) #删除的话,应该删除键
    except Exception as e:
        #记录日志 ,
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg="查询图片验证码异常")

    # 3.1.2 image_code_id没有值，表示编号过期
    if not real_image_code:
        return jsonify(errno=RET.NODATA,errmsg="验证码真实值过期")

    # 3.2 对比用户填写的真实值和后端获取的验证码真实值是否一致
    #验证码的输入可以大写,可以小写,所以比较两值时,都化成小写去比较,如下lower
    if image_code.lower() != real_image_code.lower():
        return jsonify(errno=RET.DATAERR, errmsg="图片验证码填写错误")

    # 一致：发送短信验证码
    # 查看手机号码是否注册过
    # 读数据库,使用le异常形式
    try:
        user = User.query.filter_by(mobile=mobile).first()
    except Exception as e:
        return jsonify(errno=RET.DATAERR,errmsg="查询用户手机存在异常")

    #已经注册过了
    if user:
        return jsonify(errno=RET.DATAEXIST,errmsg="用户已经注册过")


    #1,生成短信验证码随机值,此语法是前闭后开区间,本应写成1000000
    #下语法为格式化输出,位数不足6位以0补齐,
    sms_code = random.randint(0,999999)
    sms_code = "%06d"


    # 2. 调用云通讯发送短信验证码
    #验证码有效时常参数用的第三方文件constants.py 文件 ,时制是秒,所以可以除60,换成分钟,(别画蛇添足了)
    result = CCP().send_template_sms(mobile, [sms_code, constants.SMS_CODE_REDIS_EXPIRES/60], 1)

    #发送短信验证码失败
    if result != 0:
        return jsonify(errno=RET.THIRDERR, errmsg="存储短信验证码真实值失败")

    # 发送短信验证码成功
    # 需要存储短信验证码,因为后期注册需要客户再次输入短信验证码,两值做比较
    # 存储于redies ,需要写异常处理了
    try:
        redis_store.set("SMS_%s" %mobile, sms_code, ex=constants.SMS_CODE_REDIS_EXPIRES)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(erron=RET.DBERR, errmsg="存储短信验证码真实值失败")



    return jsonify(errno=RET.OK, errmsg="发送短信验证码成功，请注意查收")


# @passport_bp.route('/image_code')
# def get_imagecode():
# 前端请求接口的url,现在为127.0.0.1:5000/passport/image_code?imageCodeId=编号
@passport_bp.route('/image_code')
def get_imagecode():
    """
    图片验证码的后端接口

    0,以后视图函数接口就按这四步实现,
    1. 获取参数
        1.1 获取前端携带的imageCodeId编号
    2. 校验参数
        2.1 imageCodeId编号是否有值
    3. 逻辑处理
        3.0 生成验证码图片&验证码图片的真实值
        3.1 使用imageCodeId编号作为key存储生成的图片验证码真实值
    4. 返回值处理
        4.1 返回生成的图片
    :return:
    """

    #第一步,获取参数,获取前端携带的imageCodeId编号
    imageCodeId = request.args.get("imageCodeId")

    #第二步,较验参数,imageCodeId编号是否有值
    if not imageCodeId:
        abort(404)

    #第三步,逻辑处理
    #1,生成验证码图片 & 验证码图片的真实值
    #2,,使用imageCodeId编号作为key存储生成的图片验证码真实值
    name,text,image = captcha.generate_captcha()

    try:
        #3, 使用imageCodeId编号作为key存储生成的图片验证码真实值
        # 新考进一个文件constants.py设置有效时长按它内部定义的格式走的
        redis_store.set("imagecode_%s" % imageCodeId, text, ex=constants.IMAGE_CODE_REDIS_EXPIRES)
    except Exception as e:
        current_app.logger.error(e)
        abort(500)

    #第四步,返回值处理, 返回生成的图片
    respose = make_response(image)
    respose.headers["Content-Type"] = "image/jpg"
    return respose


@passport_bp.route('/register', methods=["POST"])
def register():
    """注册接口"""
    """
    1.获取参数
        1.1 手机号码 短信验证码 密码
    2.校验参数
        2.1 手机号码 短信验证码 密码非空判断
        2.2 手机号码验证
    3.逻辑处理
        3.1 根据手机号码的keySMS_18520340803去redis中获取真实的短信验证码
        3.2 对比用户填写的短信验证码和真实的短信验证是否一致
        3.3 一致：创建用户对象给里面的属性赋值，存储到mysql数据库User表里面去
        3.4 一般注册成功就代表你有登录过一次，可以使用session记录你的登录状态
    4.返回值处理
    """
    #1.1 手机号码 短信验证码 密码
    params_dict = request.json
    mobile = params_dict.get("mobile")
    smscode = params_dict.get("smscode")
    password = params_dict.get("password")

    #2.1 手机号码 短信验证码 密码非空判断
    if not all([mobile, smscode, password]):
        # 返回错误给前端展示
        return jsonify(errno=RET.PARAMERR, errmsg="提交参数不足")
    #2.2 手机号码格式校验
    if not re.match('1[3578][0-9]{9}', mobile):
        return jsonify(errno=RET.PARAMERR, errmsg="手机号码格式错误")

    #3.1 根据手机号码的key SMS_18520340803去redis中获取真实的短信验证码
    try:
        real_sms_code = redis_store.get("SMS_%s" % mobile)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="获取真实短信验证码异常")

    if not real_sms_code:
        # 表示短信验证码过期
        return jsonify(errno=RET.NODATA, errmsg="短信验证码过期")

    #3.2 对比用户填写的短信验证码和真实的短信验证是否一致
    if real_sms_code != smscode:
        return jsonify(errno=RET.DATAERR, errmsg="短信验证码填写错误")

    #3.3 一致：创建用户对象给里面的属性赋值，存储到mysql数据库User表里面去
    # 创建用户对象给属性赋值
    user = User()
    user.nick_name = mobile
    user.mobile = mobile

    #TODO:对密码进行赋值（加密）
    # 触发了底层的set方法进而在set方法里面进行加密处理
    user.password = password

    # 获取当前时间作为最后一次登录时间
    user.last_login = datetime.now()
    try:
        db.session.add(user)
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        # 数据库回滚
        db.session.rollback()
        return jsonify(errno=RET.DBERR, errmsg="添加用户到数据库异常")

    #3.4 一般注册成功就代表你有登录过一次，可以使用session记录你的登录状态
    session["user_id"] = user.id
    session["nick_name"] = user.nick_name
    session["mobile"] = user.mobile

    #4.返回注册成功的响应
    return jsonify(errno=RET.OK, errmsg="注册成功")



@passport_bp.route('/login',methods=["POST"])
def login():
    """用户登录接口"""
    """
    1,获取参数
        1.1 获取手机号码 密码(未加密的)
    2,校验参数
        2.1 判断是否为空
        2.2 手机号码格式校验
    3,逻辑处理
        3.1 根据手机号查询用户
        3.2 用户填写的密码与用户之前设置的密码进行比较
        3.3 若同:登录成功 使用session 记录用户信息
    4,返回值处理
    """

    # 1.1 手机号码 密码
    params_dict = request.json
    mobile = params_dict.get("mobile")
    password = params_dict.get("password")

    # 2.1 手机号码 密码非空判断
    if not all([mobile, password]):
        # 返回错误给前端展示
        return jsonify(errno=RET.PARAMERR, errmsg="提交参数不足")
    # 2.2 手机号码格式校验
    if not re.match('1[3578][0-9]{9}', mobile):
        return jsonify(errno=RET.PARAMERR, errmsg="手机号码格式错误")

    #3.1根据手机号码查用户
    try:
        user = User.query.filter_by(mobile=mobile).first()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR,errmsg="查询用户异常")

    # 用户不存在
    if not user:
        return jsonify(errno=RET.NODATA, errmsg="用户不存在")

    #3.2 用户填写的密码,和用户之前设置的密码进行对比
    #这里是密码对比方法的调用,真实方法写在了User模型类中,
    if not user.check_passowrd(password):
        return jsonify(errno=RET.PARAMERR, errmsg="密码填写错误")

    #3.2 一致: 登录成功,使用session 记录用户信息,
    session["user_id"] = user.id
    session["nick_name"] = user.nick_name
    session["mobile"] = user.mobile

    #记录最后一次登录时间()
    user.last_login = datetime.now()

    # 对模型对象进行增删改查之后,需要再次提交这种变更,关联到数据库,
    try:
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DBERR,errmsg="提交到数据库异常")

    # 如果你不想commit() 就要去设置数据库相关的配置属性(SQLALCHEMY_COMMIT_ON_TEARDOWN = False),源代码也有对此句的详解
    #翻源代码sqlalchemy可见,
    #如此配置,可去配置文件config.py 的数据库模块,配置一下,



    #4,返回值处理
    return jsonify(errno=RET.OK,errmsg="登录成功")










































